package Core.plugin

import spinal.core._
import spinal.lib._
import Core._


case class ControlPorts() extends Bundle {
    val decode_rs1_req =  Bool()
    val decode_rs2_req =  Bool()
    val decode_rs1_addr = UInt(5 bits)
    val decode_rs2_addr = UInt(5 bits)

    val rs1_from_mem = Bool()
    val rs2_from_mem = Bool()
    val rs1_from_ex = Bool()
    val rs2_from_ex = Bool()
    val rs1_load_hit = Bool()
    val rs2_load_hit = Bool()
    val ctrl_load_use = Bool()
}

trait ControlService{
    def newControlPorts() : ControlPorts
}

class ControlPlugin() extends Plugin[CPUSimple] with ControlService {
    @dontName var hazard : ControlPorts = null
    override def newControlPorts(): ControlPorts = {
        assert(hazard == null)
        hazard = ControlPorts()
        hazard
    }

    override def setup(pipeline: CPUSimple): Unit = {
        import LoongArch._
        import pipeline.config._
    }

    override def build(pipeline: CPUSimple): Unit = {
        import pipeline.config._
        import pipeline._

        if (hazard != null) pipeline plug new Area {
            hazard.rs1_from_mem := memaccess.arbitration.isValid && memaccess.output(RD_WEN) && memaccess.output(RD_ADDR) === decode.input(RS1_ADDR) && decode.input(RS1_REQ)
            hazard.rs2_from_mem := memaccess.arbitration.isValid && memaccess.output(RD_WEN) && memaccess.output(RD_ADDR) === decode.input(RS2_ADDR) && decode.input(RS2_REQ)
            hazard.rs1_from_ex := execute.arbitration.isValid && execute.output(RD_WEN) && execute.output(RD_ADDR) === decode.input(RS1_ADDR) && decode.input(RS1_REQ)
            hazard.rs2_from_ex := execute.arbitration.isValid && execute.output(RD_WEN) && execute.output(RD_ADDR) === decode.input(RS2_ADDR) && decode.input(RS2_REQ)
            hazard.rs1_load_hit := execute.output(RD_ADDR) === decode.output(RS1_ADDR) && decode.input(RS1_REQ)
            hazard.rs2_load_hit := execute.output(RD_ADDR) === decode.output(RS2_ADDR) && decode.input(RS2_REQ)
        }

        fetch plug new Area{
            import fetch._
            arbitration.haltItself := False
            arbitration.flushIt := decode.output(REDIRECT_VALID) || execute.output(INT_PC) || memaccess.output(IS_ALE)
        }

        decode plug new Area{
            import decode._
            insert(RS1_FROM_EX) := hazard.rs1_from_ex
            insert(RS1_FROM_MEM) := hazard.rs1_from_mem
            insert(RS2_FROM_EX) := hazard.rs2_from_ex
            insert(RS2_FROM_MEM) := hazard.rs2_from_mem

            arbitration.haltItself := execute.input(IS_LOAD) && (hazard.rs1_load_hit || hazard.rs2_load_hit)
            arbitration.flushIt := execute.output(INT_PC) || memaccess.output(IS_ALE)
        }

        execute plug new Area{
            import execute._
            // arbitration.haltItself :=  hazard.load_use || hazard.ctrl_load_use || output(INT_HOLD)
            arbitration.haltItself := False //output(INT_HOLD)
            arbitration.flushIt := memaccess.output(IS_ALE)
        }

        memaccess plug new Area{
            import memaccess._
            // arbitration.haltItself := hazard.load_use || hazard.ctrl_load_use || output(LSU_HOLD)
            arbitration.haltItself := output(LSU_HOLD)
            arbitration.flushIt := output(IS_ALE)
        }

        writeback plug new Area{
            import writeback._
            // arbitration.haltItself := False
            arbitration.haltItself := memaccess.output(LSU_HOLD)
            arbitration.flushIt := False
        }

    }

}
